﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Linq.Expressions;
using System.Reflection;
using System.Linq;

namespace Spotless.Services
{
    public class SortExpression<TModel>
        //where TEnumerable : IEnumerable<TModel>
        where TModel : class
    {
        public SortExpression() : this(null)
        {
        }

        private SortExpression(Dictionary<string, bool> expressions)
        {
            this.Expressions = expressions ?? new Dictionary<string, bool>();
        }

        private Dictionary<string, bool> Expressions;

        public SortExpression<TModel> SortAsc<TProperty>(System.Linq.Expressions.Expression<Func<TModel, TProperty>> expression)
        {
            AddSort(expression, true);
            return this;
        }

        public SortExpression<TModel> SortDesc<TProperty>(System.Linq.Expressions.Expression<Func<TModel, TProperty>> expression)
        {
            AddSort(expression, false);
            return this;
        }

        private void AddSort<TProperty>(System.Linq.Expressions.Expression<Func<TModel, TProperty>> expression, bool ascending)
        {
            List<string> expressionParts = new List<string>();
            Expression currentExpression = expression.Body;
            while(currentExpression != null)
            {
                if (currentExpression.NodeType == System.Linq.Expressions.ExpressionType.MemberAccess)
                {
                    MemberExpression memberExpression = (MemberExpression)currentExpression;
                    var propertyName = memberExpression.Member is PropertyInfo ? memberExpression.Member.Name : null;
                    expressionParts.Add(propertyName);
                    currentExpression = memberExpression.Expression;
                }
                else if (currentExpression.NodeType == ExpressionType.Parameter)
                {
                    currentExpression = null;
                }
                else
                {
                    throw new NotImplementedException(string.Format("Does not currently work with '{0}'", currentExpression.NodeType.ToString()));
                }
            }

            if (expressionParts.Count > 0)
            {
                StringBuilder stringExpression = new StringBuilder();
                string spacer = "";
                foreach (string part in expressionParts.Reverse<string>())
                {
                    stringExpression.Append(spacer);
                    stringExpression.Append(part);
                    spacer = ".";
                }
                Expressions.Add(stringExpression.ToString(), ascending);
            }
            //Expressions.Add(ModelMetadata.FromLambdaExpression<TModel, TProperty>(expression, null), ascending);
        }

        public string ToReverseString()
        {
            var reverse = this.ToString().Replace(" asc", " _asc");
            reverse = reverse.Replace(" desc", " asc");
            return reverse.Replace(" _asc", " desc");
        }

        public override string ToString()
        {
            var tmp = new StringBuilder();
            var spacer = "";
            foreach (var item in Expressions)
            {
                tmp.Append(spacer);
                tmp.Append(item.Key);
                tmp.Append(" ");
                tmp.Append(item.Value ? "asc" : "desc");
                spacer = ",";
            }
            return tmp.ToString();
        }

        public static SortExpression<TModel> Parse(string sortExpression)
        {
            var sorts = sortExpression
                .Split(',')
                .Select(x => x.Trim())
                .Select(x =>
                    x.EndsWith(" desc") ? new { Field = x.Substring(0, x.Length - 5), Ascending = false }
                        : x.EndsWith(" asc") ? new { Field = x.Substring(0, x.Length - 4), Ascending = true }
                            : new { Field = x, Ascending = true }
                    ).ToDictionary(x => x.Field, y => y.Ascending);
            return new SortExpression<TModel>(sorts);
        }

        public IQueryable<TModel> Sort(IQueryable<TModel> source)
        {
            IQueryable<TModel> sorted = null;
            foreach (var sort in Expressions)
            {
                var parameter = Expression.Parameter(typeof(TModel), "p");
                Expression current = null;
                Type currentType = typeof(TModel);
                var sortParts = sort.Key.Split('.');
                PropertyInfo property = null;
                foreach (var sortPart in sortParts)
                {
                    try
                    {
                        property = currentType.GetProperty(sortPart, BindingFlags.GetProperty | BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);
                    }
                    catch(AmbiguousMatchException)
                    {
                        return source;
                    }
                    if (property != null)
                    {
                        current = Expression.MakeMemberAccess(current ?? parameter, property);
                        currentType = property.PropertyType;
                    }
                }

                if (current != null && property != null)
                {
                    //Dymanically Create the linq expression
                    string methodName = string.Format("{0}By{1}", sorted != null ? "Then" : "Order", sort.Value ? "" : "Descending");

                    var orderByExp = Expression.Lambda(current, parameter);
                    MethodCallExpression resultExp = Expression.Call(
                        typeof(Queryable),
                        methodName,
                        new Type[] { typeof(TModel), property.PropertyType },
                        sorted == null ? source.Expression : sorted.Expression,
                        Expression.Quote(orderByExp));
                    sorted = source.Provider.CreateQuery<TModel>(resultExp);
                }
            }
            return sorted ?? source;
        }

    }
}